package com.nineclock.attendance.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.nineclock.attendance.dto.AttendGroupDTO;
import com.nineclock.attendance.dto.AttendPunchDTO;
import com.nineclock.attendance.dto.AttendPunchUserWholeDayDTO;
import com.nineclock.attendance.dto.AttendsDTO;
import com.nineclock.attendance.enums.AttendEnums;
import com.nineclock.attendance.enums.PunchUtils;
import com.nineclock.attendance.mapper.AttendPunchMapper;
import com.nineclock.attendance.pojo.AttendPunch;
import com.nineclock.attendance.service.AttendGroupService;
import com.nineclock.attendance.service.AttendPunchService;
import com.nineclock.common.entity.Result;
import com.nineclock.common.enums.NcException;
import com.nineclock.common.enums.ResponseEnum;
import com.nineclock.common.threadlocal.CurrentUserHolder;
import com.nineclock.common.utils.BeanHelper;
import com.nineclock.common.utils.DateTimeUtil;
import com.nineclock.system.dto.SysCompanyUserDTO;
import com.nineclock.system.feign.SysOrganizationFeign;
import com.nineclock.system.feign.SysUserFeign;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class AttendPunchServiceImpl implements AttendPunchService{

    @Autowired
    AttendPunchMapper attendPunchMapper;

    @Autowired
    AttendGroupService attendGroupService;

    /**
     * 移动端打卡
     * 打卡（封装打卡信息）
     * 1. 转换为打卡对象
     * 2. 获取用户考勤组
     * 3. 判断是否为有效打卡(是否在范围内， 是否为工作日， 是否为有效打卡（保存打卡类型）)
     * 4. 保存打卡信息
     *
     * @param attendPunchDTO
     */
    @Override
    public void punch(AttendPunchDTO attendPunchDTO){

        //健壮性判断
        if( attendPunchDTO==null ) {
            throw new NcException(ResponseEnum.INVALID_PARAM_ERROR);
        }
        //复制  DTO为 pojo
        AttendPunch attendPunch=BeanHelper.copyProperties(attendPunchDTO,AttendPunch.class);

        //查询该员工对应的 考勤组 对象
        AttendGroupDTO attendGroupDTO=attendGroupService.getAttendGroupByUserId();

        //设置属性值

        //是否在有效区域范围内
        boolean validatePunchArea=PunchUtils.validatePunchArea(attendPunchDTO,attendGroupDTO);
        if( !validatePunchArea ) {
            throw new NcException(ResponseEnum.PUNCH_INVALID_AREA);
        }
        attendPunch.setAreaValid(validatePunchArea);

        //打卡的时间 日期字符串 "yyyy-MM-dd"
        attendPunch.setPunchDateStr(DateUtil.format(new Date(),"yyyy-MM-dd"));
        attendPunch.setPunchTime(new Date());

        //设置 上下午打卡
        attendPunch.setPunchOnOffWork(DateTimeUtil.noonType(new Date())+1);


        //校验是否工作日打卡
        boolean validateWorkDay=PunchUtils.validateWorkDay(attendGroupDTO);
        if( !validateWorkDay ) {
            throw new NcException(ResponseEnum.PUNCH_INVALID_DAY);
        }

        //是否有效打卡
        boolean validatePunch=this.validatePunch(attendPunch);
        if( validatePunch ) {
            //设置 上、下 午打卡类型
            PunchUtils.setPunchType(attendPunch,attendGroupDTO);
        }

        attendPunch.setCompanyId(CurrentUserHolder.get().getCompanyId());
        attendPunch.setCompanyUserId(CurrentUserHolder.get().getCompanyUserId());
        attendPunch.setCreateTime(new Date());

        //保存打卡记录到数据库
        attendPunchMapper.insert(attendPunch);
    }

    /**
     * 获取员工当天打卡信息
     *
     * @return
     */
    @Override
    public AttendPunchUserWholeDayDTO queryPunchRecord(){

        AttendPunchUserWholeDayDTO wholeDayDTO=new AttendPunchUserWholeDayDTO();

        //获取企业员工ID , 企业ID , 当前日期字符串
        Long companyId=CurrentUserHolder.get().getCompanyId();
        Long companyUserId=CurrentUserHolder.get().getCompanyUserId();
        String nowStr=DateTimeUtil.dateToStr(new Date(),DateTimeUtil.TIME_FORMAT_2);

        //1. 查询上午有效打卡情况
        LambdaQueryWrapper <AttendPunch> attendPunchQW=new LambdaQueryWrapper <>();
        attendPunchQW.eq(AttendPunch::getCompanyId,companyId)
                .eq(AttendPunch::getCompanyUserId,companyUserId)
                .eq(AttendPunch::getEffectiveValid,true)
                .eq(AttendPunch::getPunchOnOffWork,AttendEnums.NOON_TYPE_MORNING.value())
                .eq(AttendPunch::getPunchDateStr,nowStr);
        AttendPunch amPunch=attendPunchMapper.selectOne(attendPunchQW);

        //1. 查询下有效打卡情况
        LambdaQueryWrapper <AttendPunch> pmPunchQW=new LambdaQueryWrapper <>();
        pmPunchQW.eq(AttendPunch::getCompanyId,companyId)
                .eq(AttendPunch::getCompanyUserId,companyUserId)
                .eq(AttendPunch::getEffectiveValid,true)
                .eq(AttendPunch::getPunchOnOffWork,AttendEnums.NOON_TYPE_AFTERNOON.value())
                .eq(AttendPunch::getPunchDateStr,nowStr);
        List <AttendPunch> attendPunches=attendPunchMapper.selectList(pmPunchQW);
        AttendPunch pmPunch=attendPunchMapper.selectOne(pmPunchQW);

        wholeDayDTO.setAttendPunchMorningResponse(BeanHelper.copyProperties(amPunch,AttendPunchDTO.class));

        wholeDayDTO.setAttendPunchAfterNoonResponse(BeanHelper.copyProperties(pmPunch,AttendPunchDTO.class));

        return wholeDayDTO;
    }

    @Autowired
    SysOrganizationFeign organizationFeign;

    /**
     * 查询指定时间段内的考勤列表数据
     *
     * @param startTime
     * @param endTime
     * @return
     */
    @Override
    public List <AttendPunchDTO> queryMembers(String startTime,String endTime){
        //A. 获取两个日期时间范围之间的日期列表
        List <String> spanDateList=this.getSpanDateList(startTime,endTime);
        List <SysCompanyUserDTO> companyUserDTOS=organizationFeign.queryAllCompanyUser().getData();

        List <AttendPunchDTO> attendPunchDTOS=getAttendPunchDTOS(startTime,endTime,spanDateList,companyUserDTOS);
        return attendPunchDTOS;
    }

    /**
     * 导出指定时间段之内的考勤数据
     *
     * @param startTime
     * @param endTime
     */
    @Override
    public void exportAttendData(String startTime,String endTime) throws IOException{
        List <AttendPunchDTO> dtoList=this.queryMembers(startTime,endTime);
        HttpServletResponse response=((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();

        response.setHeader("Content-disposition","attachment;filename=Attendance.xlsx");
        response.setContentType("application/octet-stream;charset=UTF-8");
        response.setCharacterEncoding("utf-8");

        EasyExcel.write(response.getOutputStream())
                .excelType(ExcelTypeEnum.XLS)
                .head(handleHeader(dtoList))
                .registerWriteHandler(new CustomHandler())
                .registerWriteHandler(horizontalCellStyleStrategy())
                .sheet("考勤记录")
                .doWrite(handleData(dtoList));


    }

    @Autowired
    SysUserFeign sysUserFeign;

    /**
     * 根据企业ID查询该企业本月考勤信息, 并生成Excel文件
     *
     * @param companyId
     * @return
     */
    @Override
    public String generateExcelReportByCompanyId(Long companyId){

        //获取本月的第一天和最后一天
        String firstDay=DateTimeUtil.getCurrentMonthFirstDay();
        String lastDay=DateTimeUtil.getCurrentMonthLastDay();

        List <String> spanDateList=this.getSpanDateList(firstDay,lastDay);

        List <SysCompanyUserDTO> userDTOSList=sysUserFeign.queryCompanyUser(companyId,null).getData();

        if(CollectionUtil.isEmpty(userDTOSList)) {
            return null;
        }
        List <AttendPunchDTO> attendPunchDTOS=this.getAttendPunchDTOS(firstDay,lastDay,spanDateList,userDTOSList);

        String foldername="D:/考勤数据/"+companyId;
        String filename=foldername+"/"+userDTOSList.get(0).getCompanyName()+"-"+firstDay.substring(0,7)+"-考勤数据.xlsx";
        if( ! new File(foldername).exists() ) {
            new File(foldername).mkdirs();
        }
        EasyExcel.write(filename)
                .excelType(ExcelTypeEnum.XLS)
                .head(handleHeader(attendPunchDTOS))
                .registerWriteHandler(new CustomHandler())
                .registerWriteHandler(horizontalCellStyleStrategy())
                .sheet("考勤数据")
                .doWrite(handleData(attendPunchDTOS));
        return filename;
    }

    //设置样式
    public HorizontalCellStyleStrategy horizontalCellStyleStrategy(){

        WriteCellStyle contentWriteCellStyle=new WriteCellStyle();
        //内容策略 - 水平居中
        contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);

        //设置字体
        WriteFont font=new WriteFont();
        font.setFontName("阿里巴巴普惠体");
        font.setFontHeightInPoints((short) 11);
        contentWriteCellStyle.setWriteFont(font);

        HorizontalCellStyleStrategy horizontalCellStyleStrategy=new HorizontalCellStyleStrategy(contentWriteCellStyle,contentWriteCellStyle);

        return horizontalCellStyleStrategy;
    }

    //组装表头信息
    private List <List <String>> handleHeader(List <AttendPunchDTO> punchDTOS){
        List <List <String>> headers=new ArrayList <List <String>>();

        headers.add(Arrays.asList("姓名"));
        headers.add(Arrays.asList("工号"));
        headers.add(Arrays.asList("部门"));
        headers.add(Arrays.asList("职位"));

        //动态表头
        AttendPunchDTO attendPunchDTO=punchDTOS.get(0);
        if( attendPunchDTO!=null ) {
            List <AttendsDTO> attendsList=attendPunchDTO.getAttendsList();
            if( CollectionUtil.isNotEmpty(attendsList) ) {
                for(AttendsDTO attendsDTO : attendsList) {
                    headers.add(Arrays.asList(attendsDTO.getAttendDate()));
                }
            }
        }

        return headers;
    }

    //组装数据
    private List <List <String>> handleData(List <AttendPunchDTO> punchDTOS){
        List <List <String>> dataList=new ArrayList <List <String>>();

        if( CollectionUtil.isNotEmpty(punchDTOS) ) {
            for(AttendPunchDTO punchDTO : punchDTOS) {
                //姓名, 工号, 部门, 职位
                List <String> data=new ArrayList <String>();
                data.add(punchDTO.getUserName());
                data.add(punchDTO.getWorkNumber());
                data.add(punchDTO.getDepartmentName());
                data.add(punchDTO.getPost());

                //动态信息 : 每日打卡情况
                List <AttendsDTO> attendsList=punchDTO.getAttendsList();
                if( CollectionUtil.isNotEmpty(attendsList) ) {
                    for(AttendsDTO dto : attendsList) {
                        data.add(dto.getAttendStatus());
                    }
                }
                dataList.add(data);
            }
        }

        return dataList;
    }

    //获取指定日期范围内, 指定员工的考勤打卡情况
    private List <AttendPunchDTO> getAttendPunchDTOS(String startTime,String endTime,List <String> spanDateList,List <SysCompanyUserDTO> companyUserDTOS){
        return companyUserDTOS.stream().map(sysCompanyUserDTO->{
            //封装用户信息
            AttendPunchDTO attendPunchDTO=BeanHelper.copyProperties(sysCompanyUserDTO,AttendPunchDTO.class);

            //获取该用户的指定时间范围内的有效打卡数据
            LambdaQueryWrapper <AttendPunch> attendPunchQueryWrapper=new LambdaQueryWrapper <>();
            attendPunchQueryWrapper.eq(AttendPunch::getCompanyUserId,sysCompanyUserDTO.getId());
            attendPunchQueryWrapper.eq(AttendPunch::getEffectiveValid,true);
            attendPunchQueryWrapper.between(AttendPunch::getPunchDateStr,startTime,endTime);

            //执行查询考勤数据操作, 查询出来的数据 , 每一天会包含两条数据(上午打卡, 下午打卡)
            List <AttendPunch> attendPunches=attendPunchMapper.selectList(attendPunchQueryWrapper);

            //对获取到的打卡数据进行处理
            List <AttendsDTO> attendsDTOS=this.handleAttendPunchEveryDay(spanDateList,attendPunches);

            attendPunchDTO.setAttendsList(attendsDTOS);

            return attendPunchDTO;
        }).collect(Collectors.toList());
    }

    /**
     * 对获取到的打卡数据进行处理
     *
     * @param spanDateList
     * @param attendPunches
     * @return
     */
    private List <AttendsDTO> handleAttendPunchEveryDay(List <String> spanDateList,List <AttendPunch> attendPunches){
        //将获取到的打卡信息列表进行分组处理
        Map <String,List <AttendPunch>> punchMap=attendPunches.stream().collect(Collectors.groupingBy(AttendPunch::getPunchDateStr));

        //获取每一天的打卡情况
        List <AttendsDTO> attendsDTOS=spanDateList.stream().map(dateStr->{
            AttendsDTO attendsDTO=new AttendsDTO();
            attendsDTO.setAttendDate(dateStr);

            List <AttendPunch> punches=punchMap.get(dateStr);//获取到某一天的打卡数据
            //如果为空, 则说明上下午都没有打卡
            if( CollectionUtil.isEmpty(punches) ) {
                attendsDTO.setAttendStatus("缺卡/缺卡");
            } else {
                attendsDTO.setAttendStatus(this.handleAttendStatus(punches));
            }
            return attendsDTO;
        }).collect(Collectors.toList());

        return attendsDTOS;
    }

    /**
     * 是否打卡
     *
     * @param punches
     * @return
     */
    private String handleAttendStatus(List <AttendPunch> punches){
        String amAttendStatus="缺卡";//上午 - 考勤
        String pmAttendStatus="缺卡";//下午 - 考勤
        if( CollectionUtil.isNotEmpty(punches) ) {
            for(AttendPunch punch : punches) {
                if( punch.getPunchOnOffWork()==AttendEnums.NOON_TYPE_MORNING.value() ) {
                    amAttendStatus=this.getPushDesc(punch.getMorningPunchType());
                } else if( punch.getPunchOnOffWork()==AttendEnums.NOON_TYPE_AFTERNOON.value() ) {
                    pmAttendStatus=this.getPushDesc(punch.getAfternoonPunchType());
                }
            }
        }
        return amAttendStatus+"/"+pmAttendStatus;
    }


    private String getPushDesc(Integer morningPunchType){
        String desc="";
        switch(morningPunchType){
            case 1:
                desc=AttendEnums.PUNCH_TYPE_OK.toString();
                break;
            case 2:
                desc=AttendEnums.PUNCH_TYPE_LATE.toString();
                break;
            case 3:
                desc=AttendEnums.PUNCH_TYPE_EARLY.toString();
                break;
            case 4:
                desc=AttendEnums.PUNCH_TYPE_STAYALAY.toString();
                break;
            case 5:
                desc=AttendEnums.PUNCH_TYPE_NOT_WORK_DAY.toString();
                break;
            default:
                break;
        }
        return desc;
    }

    /**
     * 获取两个日期时间范围之间的日期列表
     *
     * @param startTime
     * @param endTime
     * @return
     */
    private List <String> getSpanDateList(String startTime,String endTime){
        List <Date> dateList=DateTimeUtil.getDatesBetweenTwoDate(startTime,endTime,DateTimeUtil.TIME_FORMAT_2);
        List <String> dateStrList=dateList.stream().map(date->DateTimeUtil.dateToStr(date,DateTimeUtil.TIME_FORMAT_2)).collect(Collectors.toList());
        return dateStrList;
    }

    /**
     * 判断是否为有效打卡
     *
     * @param attendPunch
     * @return
     */
    private boolean validatePunch(AttendPunch attendPunch){

        LambdaQueryWrapper <AttendPunch> punchQw=new LambdaQueryWrapper <>();

        punchQw.eq(AttendPunch::getPunchOnOffWork,attendPunch.getPunchOnOffWork());
        punchQw.eq(AttendPunch::getCompanyId,attendPunch.getCompanyId());
        punchQw.eq(AttendPunch::getCompanyUserId,attendPunch.getCompanyUserId());
        punchQw.eq(AttendPunch::getPunchDateStr,attendPunch.getPunchDateStr());

        Integer count=attendPunchMapper.selectCount(punchQw);

        if( count==0 ) {
            attendPunch.setEffectiveValid(true);
        } else {
            if( attendPunch.getPunchOnOffWork()==1 ) {
                attendPunch.setEffectiveValid(false);
            } else {
                attendPunch.setEffectiveValid(true);
                AttendPunch updateAttendPunch=new AttendPunch();
                updateAttendPunch.setEffectiveValid(false);
                attendPunchMapper.update(updateAttendPunch,punchQw);
            }

        }


        return attendPunch.getEffectiveValid();
    }
}
